/*
** Copyright (C) 2011 Michael Zhang <zhangfuxi@gmail.com>
**  
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
** 
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software 
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/

/* ifd.cc -- routine file for IFD library.
 * 
 * ifd.cc, v0.1 2011-09-14 Michael.Zhang
 *
 * history:
 * v0.1 2011-09-14 Michael.Zhang
 *        * Creation
 * 
 */
#include "ifd.h"

#include <opensc/opensc.h>
#include "ifd_util.h"

#define IFD_APP_NAME "ifd_lib"

static sc_context_t *ctx    = NULL;
static sc_card_t    *card   = NULL;

/* Initialize IFD environment.
 * Returns 0 if successful, or a negative value in case of error.
 */
int InitializeIFD(void)
{
  return sc_establish_context(&ctx, IFD_APP_NAME);
}

void FinalizationIFD(void)
{
  if (ctx != NULL) {
    sc_release_context(ctx);
    ctx = NULL;
  }
}

char **ListIFD(void)
{
  char **ifd_names = NULL;

  if (ctx == NULL) {
    if (InitializeIFD() != 0) {
      ifd_names = malloc((sizeof(char*)));
      ifd_names[0] = NULL;
      return ifd_names;
    }
  }

  ifd_names = malloc((sc_ctx_get_reader_count(ctx)+1)*(sizeof(char*)));
  ifd_names[sc_ctx_get_reader_count(ctx)] = NULL;
  
  int i;
  for (i=0; i<sc_ctx_get_reader_count(ctx); i++) {
    ifd_names[i] = strdup(sc_ctx_get_reader(ctx, i)->name);
  }

  return ifd_names;
}

int OpenIFD(char *ifd_name)
{
  int i = 0;
  sc_reader_t *reader = NULL;
  for (i=0; i<sc_ctx_get_reader_count(ctx); i++) {
    reader = sc_ctx_get_reader(ctx, i);
    if (strcmp(reader->name, ifd_name) == 0)
      return OpenIFDByID(i);
  }
  return -1;
}

int OpenIFDByID(int ifd_id)
{
  sc_reader_t *reader = sc_ctx_get_reader(ctx, ifd_id);

  return sc_connect_card(reader, 0, &card);
}

int ResetIFD(void)
{
  if (card == NULL) return -1;

  return sc_reset(card);
}

int CloseIFD(void)
{
  if (card == NULL) return 0;
  return sc_disconnect_card(card, 0);
}

void GetATR(char *atr)
{
  sc_bin_to_hex(card->atr, card->atr_len, atr, SC_MAX_ATR_SIZE*2+1, '\0');
}

void TransmitAPDU(char *c_apdu, char *r_apdu)
{
  sc_apdu_t apdu;
  u8 buf[SC_MAX_APDU_BUFFER_SIZE], *p;
  u8 sbuf[SC_MAX_APDU_BUFFER_SIZE];
  u8 rbuf[SC_MAX_APDU_BUFFER_SIZE];
  int len = sizeof(buf), r;

  p = buf;
  sc_hex_to_bin(c_apdu, p, &len);
  memset(&apdu, 0, sizeof(apdu));
  apdu.cla = *(p++);
  apdu.ins = *(p++);
  apdu.p1 = *(p++);
  apdu.p2 = *(p++);
  apdu.resp = rbuf;
  apdu.resplen = sizeof(rbuf);
  len -= 4;
  if (len > 1) {
    apdu.lc = *p++;
    len--;
    memcpy(sbuf, p, apdu.lc);
    apdu.data = sbuf;
    apdu.datalen = apdu.lc;
    if (len < apdu.lc) {
      sprintf(r_apdu, "APDU too short (need %lu bytes).",
              (unsigned long) apdu.lc-len);
      return;
    }
    len -= apdu.lc;
    p += apdu.lc;
    if (len) {
      apdu.le = *(p++);
      if (apdu.le == 0) apdu.le = 256;
      len--;
      apdu.cse = SC_APDU_CASE_4_SHORT;
    } else
      apdu.cse = SC_APDU_CASE_3_SHORT;
    if (len) {
      sprintf(r_apdu, "APDU too long (%lu bytes extra).",
              (unsigned long) len);
      return;
    }
  } else if (len == 1) {
    apdu.le = *(p++);
    if (apdu.le == 0) apdu.le = 256;
    len--;
    apdu.cse = SC_APDU_CASE_2_SHORT;
  } else
    apdu.cse = SC_APDU_CASE_1;

  r = sc_transmit_apdu(card, &apdu);
  if (r) {
    sprintf(r_apdu, "APDU transmit failed: %s\n", sc_strerror(r));
    return;
  }

  if (apdu.resplen)
    sc_bin_to_hex(apdu.resp, apdu.resplen, r_apdu, 256*2+1, '\0');
  sprintf(r_apdu, "%s%02X%02X", r_apdu, apdu.sw1, apdu.sw2);
}
